﻿using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
using ru.tcl.dxf;
using DxfEntities = ru.tcl.dxf.entities;
using System.IO;
using System.Windows.Forms;

namespace Atechnology.glassCAD.Shapes.Dxf {

    public enum Corner
    {
        LeftUp, 
        LeftDown, 
    }

    public interface Primitive {
        float getRight();
        float getLeft();
        float getTop();
        float getBottom();
        void MirrorHorizontal(float Y);
        void MirrorVertical(float X);
        PointF getStart();
        PointF getEnd();
        double Length();
    }

    public class Line : Primitive {
        public Line(PointF Start, PointF End) {
            this.Start = Start;
            this.End = End;
        }

        public float getRight() {
            return System.Math.Max(Start.X, End.X);
        }

        public float getLeft() {
            return System.Math.Min(Start.X, End.X);
        }

        public float getTop() {
            return System.Math.Max(Start.Y, End.Y);
        }

        public float getBottom() {
            return System.Math.Min(Start.Y, End.Y);
        }

        public double Length() {
            return System.Math.Sqrt((Start.X - End.X) * (Start.X - End.X) + (Start.Y - End.Y) * (Start.Y - End.Y));
        }

        public void MirrorHorizontal(float X) {
            Start.X = (2 * X - Start.X);
            End.X = (2 * X - End.X);
        }

        public void MirrorVertical(float Y) {
            Start.Y = (2 * Y - Start.Y);
            End.Y = (2 * Y - End.Y);
        }

        public PointF getStart() {
            return Start;
        }

        public PointF getEnd() {
            return End;
        }

        public PointF Start;
        public PointF End;
    }

    public class Arc : Primitive {
        public Arc(PointF Center, float Radius, float Start, float End) {
            this.Start = Start;
            this.End = End;
            this.Center = Center;
            this.Radius = Radius;
        }

        public float getRight() {
            double start = Start / 180 * System.Math.PI;
            double end = End / 180 * System.Math.PI;
            if(end < start)
                end += 2 * System.Math.PI;
            if(end >= 2 * System.Math.PI)
                return Center.X + Radius;

            float startPointX = (float)(Center.X + Radius * System.Math.Cos(start));
            float endPointX = (float)(Center.X + Radius * System.Math.Cos(end));

            return System.Math.Max(startPointX, endPointX);
        }

        public float getLeft() {
            double start = Start / 180 * System.Math.PI;
            double end = End / 180 * System.Math.PI;
            if(end < start)
                end += 2 * System.Math.PI;
            if(end >= 3 * System.Math.PI)
                return Center.X - Radius;
            if(start <= System.Math.PI && end >= System.Math.PI)
                return Center.X - Radius;

            float startPointX = (float)(Center.X + Radius * System.Math.Cos(start));
            float endPointX = (float)(Center.X + Radius * System.Math.Cos(end));
            return System.Math.Min(startPointX, endPointX);
        }

        public float getTop() {
            double start = Start / 180 * System.Math.PI;
            double end = End / 180 * System.Math.PI;
            if(end < start)
                end += 2 * System.Math.PI;
            if(end >= 2.5 * System.Math.PI)
                return Center.Y + Radius;
            if(start <= System.Math.PI / 2 && end >= System.Math.PI / 2)
                return Center.Y + Radius;

            float startPointY = (float)(Center.Y + Radius * System.Math.Sin(start));
            float endPointY = (float)(Center.Y + Radius * System.Math.Sin(end));
            return System.Math.Max(startPointY, endPointY);
        }

        public float getBottom() {
            double start = Start / 180 * System.Math.PI;
            double end = End / 180 * System.Math.PI;
            if(end < start)
                end += 2 * System.Math.PI;
            if(end >= 3.5 * System.Math.PI)
                return Center.Y - Radius;
            if(start <= 3 * System.Math.PI / 2 && end >= 3 * System.Math.PI / 2)
                return Center.Y - Radius;

            float startPointY = (float)(Center.Y + Radius * System.Math.Sin(start));
            float endPointY = (float)(Center.Y + Radius * System.Math.Sin(end));
            return System.Math.Min(startPointY, endPointY);
        }


        public PointF getStart() {
            double start = Start / 180 * System.Math.PI;
            return new PointF((float)(Center.Y + Radius * System.Math.Cos(start)),
                              (float)(Center.Y + Radius * System.Math.Sin(start)));
        }

        public PointF getEnd() {
            double end = End / 180 * System.Math.PI;
            return new PointF((float)(Center.Y + Radius * System.Math.Cos(end)),
                              (float)(Center.Y + Radius * System.Math.Sin(end)));
        }

        private float MirrorAngleH(float a) {
            if(a < 180)
                return 180 - a;
            else
                return 540 - a;
        }

        private float MirrorAngleV(float a) {
            return 360 - a;
        }

        public double Length() {
            return 2 * System.Math.PI * Radius * ((End - Start) / 360);
        }

        public void MirrorHorizontal(float X) {
            Center.X = (2 * X - Center.X);
            if(End - Start < 360) {
                Start = MirrorAngleH(Start);
                End = MirrorAngleH(End);
                if(End < Start) {
                    float temp = Start;
                    Start = End;
                    End = temp;
                }
            }
        }

        public void MirrorVertical(float Y) {
            Center.Y = (2 * Y - Center.Y);
            Start = MirrorAngleV(Start);
            End = MirrorAngleV(End);
            if(End - Start < 360) {
                if(End < Start) {
                    float temp = Start;
                    Start = End;
                    End = temp;
                }
            }
        }

        public PointF Center;
        public float Radius;
        public float Start;
        public float End;
    }

    public class Dxf : List<Primitive> {
        public Dxf() {

        }
        //public Dxf(string dxf) {
        //    DXFDocument doc = new DXFDocument();            

        //   // doc.parseDXF(dxf);
        //    Load(doc.getEntities().items());
        //}

        public void Load(java.io.File file)
        {
            this.Clear();

            DXFDocument doc = new DXFDocument();
            try
            {

                if (file.exists())
                    doc.readDXF(file);
                else
                    MessageBox.Show("Файл не найден");
            }
            catch (java.lang.Exception e)
            {
                string str = e.Message + " |||| ";
                Exception innerEx = e.InnerException;
                while (innerEx != null)
                {
                    str += innerEx.Message + " |||| ";
                    innerEx = innerEx.InnerException;
                }
                MessageBox.Show(str);
            }

            Load(doc.getEntities().items());
        }

        //public void Load(string dxf) {
        //    this.Clear();

        //    DXFDocument doc = new DXFDocument();            

        //    doc.parseDXF(dxf);
        //    Load(doc.getEntities().items());
        //}

        private bool filled;

        public virtual bool isFilled() {
            return filled;
        }

        private void Load(java.util.Enumeration items) {
            filled = true;
            while(items.hasMoreElements()) {
                object obj = items.nextElement();
                if(obj is DxfEntities.Line) {
                    DxfEntities.Line line = obj as DxfEntities.Line;
                    Add(new Line(new PointF((float)line.getStart().getX(), (float)line.getStart().getY()),
                                 new PointF((float)line.getEnd().getX(), (float)line.getEnd().getY())));
                    filled = false;

                } else if(obj is DxfEntities.Arc) {
                    DxfEntities.Arc arc = obj as DxfEntities.Arc;
                    Add(new Arc(new PointF((float)arc.getCenter().getX(), (float)arc.getCenter().getY()), (float)arc.getRadius(), (float)arc.getStartAngle(), (float)arc.getEndAngle()));
                    filled = false;
                } else if(obj is DxfEntities.Circle) {
                    DxfEntities.Circle arc = obj as DxfEntities.Circle;
                    Add(new Arc(new PointF((float)arc.getCenter().getX(), (float)arc.getCenter().getY()), (float)arc.getRadius(), 0, 360));
                    filled = false;
                } else if(obj is DxfEntities.Polyline) {
                    DxfEntities.Vertex a = null;
                    DxfEntities.Vertex b = null;
                    java.util.Enumeration vertexes = (obj as DxfEntities.Polyline).items();
                    DxfEntities.Vertex startElem = null;
                    int counter = 0;
                    List<DxfEntities.Vertex> verteces = new List<ru.tcl.dxf.entities.Vertex>();
                    while(vertexes.hasMoreElements()) 
                    {
                        object elem = vertexes.nextElement();
                        
                        if(elem is DxfEntities.Vertex) {
                            verteces.Add(elem as DxfEntities.Vertex);
                            //counter++;
                            //a = b;
                            //b = elem as DxfEntities.Vertex;
                            //if(startElem == null)
                            //    startElem = b;
                            //if(a != null) {
                            //    Add(new Line(new PointF((float)a.getLocation().getX(), (float)a.getLocation().getY()),
                            //                  new PointF((float)b.getLocation().getX(), (float)b.getLocation().getY())));
                            //}
                        }
                    }
                    //a = b;
                    //b = startElem;
                    //Add(new Line(new PointF((float)a.getLocation().getX(), (float)a.getLocation().getY()),
                    //  new PointF((float)b.getLocation().getX(), (float)b.getLocation().getY())));

             //       Add(new Line(new PointF((float)
                    if (verteces.Count == 0)
                        return;
                    DxfEntities.Vertex prevVertex = verteces[0];
                    for (int i = 1; i < verteces.Count; i++)
                    {
                        double bulge = prevVertex.getBulge();
                        if (bulge == 0)
                        {
                            this.AddLine(prevVertex, verteces[i]);
                            //Add(new Line(new PointF((float)prevVertex.getLocation().getX(), (float)prevVertex.getLocation().getY()),
                            //             new PointF((float)verteces[i].getLocation().getX(), (float)verteces[i].getLocation().getY())));
                        }
                        else
                        {
                            this.AddArc(prevVertex, verteces[i]);
                        }
                     
                        prevVertex = verteces[i];
                    }


                    double _bulge = verteces[verteces.Count - 1].getBulge();
                    if (_bulge == 0)
                    {
                        this.AddLine(verteces[verteces.Count - 1], verteces[0]);
                    }
                    else
                    {
                        this.AddArc(verteces[verteces.Count - 1], verteces[0]);
                    }

                    //Add(new Line(new PointF((float)verteces[verteces.Count - 1].getLocation().getX(), (float)verteces[verteces.Count - 1].getLocation().getY()),
                    //                 new PointF((float)verteces[0].getLocation().getX(), (float)verteces[0].getLocation().getY())));
                }
            }
        }

        private void AddLine(ru.tcl.dxf.entities.Vertex vertex1, ru.tcl.dxf.entities.Vertex vertex2)
        {
            PointF point1 = new PointF((float)vertex1.getLocation().getX(), (float)vertex1.getLocation().getY());
            PointF point2 = new PointF((float)vertex2.getLocation().getX(), (float)vertex2.getLocation().getY());

            Add(new Line(point1, point2));

        }

        private void AddArc(ru.tcl.dxf.entities.Vertex vertex1, ru.tcl.dxf.entities.Vertex vertex2)
        {
            double bulge = vertex1.getBulge();
            double c = Math.Sqrt(Math.Pow(vertex1.getLocation().getX() - vertex2.getLocation().getX(), 2) + Math.Pow(vertex1.getLocation().getY() - vertex2.getLocation().getY(), 2));
            double prevX = vertex1.getLocation().getX();
            double prevY = vertex1.getLocation().getY();
            double x = vertex2.getLocation().getX();
            double y = vertex2.getLocation().getY();
            //if (bulge < 0)
                //MessageBox.Show("Вы задали контур по часовой стрелке. Для правильной загрузки фигуры в редактор раскладки необходимо задать контур против часовой стрелки");
            //if (bulge > 1)
                //MessageBox.Show("Арки, высота которых больше их радиуса, не поддерживаются.");

            double s = c / 2 * Math.Abs(bulge);
            double radius = (Math.Pow(c / 2, 2) + Math.Pow(s, 2)) / (2 * s);
            double _a = radius - s;
            double midPointX = (prevX + x) / 2;
            double midPointY = (prevY + y) / 2;
            ModelVector vector = new ModelVector(new ModelPoint(prevX, prevY), new ModelPoint(x, y));
            if (bulge > 0)
                vector = vector.Rotate(new Angle(Math.PI / 2));
            else
                vector = vector.Rotate(new Angle(-Math.PI / 2));
            vector.Length = _a;
            ModelPoint midPoint = new ModelPoint(midPointX, midPointY);
            ModelPoint center = ModelCalculations.GetOffsetPoint(midPoint, vector);
            ModelPoint startPoint;
            ModelPoint endPoint;
            if (bulge > 0)
            {
                startPoint = new ModelPoint(prevX, prevY);
                endPoint = new ModelPoint(x, y);
            }
            else
            {
                startPoint = new ModelPoint(x, y);
                endPoint = new ModelPoint(prevX, prevY);

            }
            Angle startAngle = ModelCalculations.GetAngleBetweenVectors(new ModelVector(1, 0), new ModelVector(center, startPoint));
            Angle endAngle = ModelCalculations.GetAngleBetweenVectors(new ModelVector(1, 0), new ModelVector(center, endPoint));

            Add(new Arc(new PointF((float)center.X, (float)center.Y), (float)radius, (float)(startAngle.Value * 180 / Math.PI), (float)(endAngle.Value * 180 / Math.PI)));

        }

        public RectangleF CalcRect(Corner corner) {
            float top = int.MinValue;
            float bottom = int.MaxValue;
            float left = int.MaxValue;
            float right = int.MinValue;


            foreach(Primitive p in this) {
                float item_top = p.getTop();
                float item_bottom = p.getBottom();
                float item_left = p.getLeft();
                float item_right = p.getRight();
                if(item_top > top)
                    top = item_top;
                if(item_right > right)
                    right = item_right;
                if(item_left < left)
                    left = item_left;
                if(item_bottom < bottom)
                    bottom = item_bottom;
            }
            switch (corner)
            {
                case Corner.LeftDown:
                    return new RectangleF(left, bottom, right - left, top - bottom);
                case Corner.LeftUp:
                    return new RectangleF(left, top, right - left, top - bottom);
                default:
                    return new RectangleF(left, bottom, right - left, top - bottom);
            }
            
        }

        public void MirrorVertical(float X) {
            foreach(Primitive p in this) {
                p.MirrorVertical(X);
            }
        }

        public void MirrorHorizontal(float Y) {
            foreach(Primitive p in this) {
                p.MirrorHorizontal(Y);
            }
        }

        public double Length() {
            double result = 0;
            foreach(Primitive p in this) {
                result += p.Length();
            }
            return result;
        }

    }

    public class DxfDrawer {
        public struct State {
            public double scaleX;
            public double scaleY;
            public double translateX;
            public double translateY;
            public double angle;
            public double centerX;
            public double centerY;
            public double ofsX;
            public double ofsY;

            public void Rotate(double x, double y, double angle, out double rx, out double ry) {
                rx = x * System.Math.Cos(angle) - y * System.Math.Sin(angle);
                ry = x * System.Math.Sin(angle) + y * System.Math.Cos(angle);
            }

            public PointF ConvertPoint(PointF point) {
                double rx;
                double ry;

                Rotate(point.X - centerX + ofsX, point.Y - centerY + ofsY, angle, out rx, out ry);
                return new PointF((float)System.Math.Round(((rx + centerX) * scaleX + translateX)), (float)System.Math.Round(((ry + centerY) * scaleY + translateY)));
            }

            public float ConvertWidth(double width) {
                return (float)(width * scaleX);
            }

            public float ConvertHeight(double height) {
                return (float)(height * scaleY);
            }
        }

        public static void Draw(Graphics gfx, Dxf shape, State state, Color fillColor) {
            if(shape.isFilled()) {
                DrawFillPoly(gfx, shape, state, fillColor);
              //  return;
            }

            foreach(Primitive p in shape) {
                if(p is Line) {
                    DrawLine(gfx, p as Line, state);
                } else if(p is Arc) {
                    DrawArc(gfx, p as Arc, state);
                }
            }
        }

        private static void DrawLine(Graphics gfx, Line line, State state) {
            PointF start = state.ConvertPoint(line.Start);
            PointF end = state.ConvertPoint(line.End);
            gfx.DrawLine(Pens.Black, start, end);
        }

        private static void DrawArc(Graphics gfx, Arc arc, State state) {
            PointF center = state.ConvertPoint(arc.Center);
            float width = System.Math.Abs(state.ConvertWidth(arc.Radius));
            float height = System.Math.Abs(state.ConvertHeight(arc.Radius));
            float sweep = (float)(arc.End - arc.Start);
            if(sweep < 0)
                sweep += 360;
            if(sweep <= 0)
                sweep += 360;
            gfx.DrawArc(Pens.Black, center.X - width, center.Y - height, width * 2, height * 2, -(float)(state.angle / System.Math.PI * 180 + arc.Start), -sweep);
        }

        private static void DrawFillPoly(Graphics gfx, Dxf shape, State state, Color fillColor) {
            List<PointF> points = new List<PointF>();

            List<PointF> pointsArc = new List<PointF>();
            System.Drawing.Drawing2D.GraphicsPath path;

            bool first = true;
           // bool second = true;
            foreach(Primitive primitive in shape) {
                if (primitive is Line)
                {
                    if (first)
                    {
                        points.Add(state.ConvertPoint(((Line)primitive).Start));
                        first = false;
                    }
                    points.Add(state.ConvertPoint(((Line)primitive).End));
                }
                else if (primitive is Arc)
                {
                    path = new System.Drawing.Drawing2D.GraphicsPath();
                    Arc arc = (Arc)primitive;

                    PointF center = state.ConvertPoint(arc.Center);
                    float width = System.Math.Abs(state.ConvertWidth(arc.Radius));
                    float height = System.Math.Abs(state.ConvertHeight(arc.Radius));
                    float sweep = (float)(arc.End - arc.Start);
                    if (sweep < 0)
                        sweep += 360;
                    if (sweep <= 0)
                        sweep += 360;
                    float startAngle = (float)(arc.Start * Math.PI / 180);
                    float endAngle = (float)(arc.End * Math.PI / 180);
                    path.AddArc(center.X - width, center.Y - height, width * 2, height * 2, -(float)(state.angle / System.Math.PI * 180 + arc.Start), -sweep);
                    ModelPoint startPoint = new ModelPoint(arc.Center.X + arc.Radius, arc.Center.Y);
                    startPoint = ModelCalculations.RotatePoint(startPoint, new ModelPoint(arc.Center.X, arc.Center.Y), startAngle);
                    ModelPoint endPoint = new ModelPoint(arc.Center.X + arc.Radius, arc.Center.Y);
                    endPoint = ModelCalculations.RotatePoint(endPoint, new ModelPoint(arc.Center.X, arc.Center.Y), endAngle);
                    PointF point1 = state.ConvertPoint(new PointF((float)startPoint.X, (float)startPoint.Y));
                    PointF point2 = state.ConvertPoint(new PointF((float)endPoint.X, (float)endPoint.Y));
                    path.AddLine(point1, point2);
                    gfx.FillPath(Brushes.White, path);                   
                    
                }
            }

            //Point[] intPoints = new Point[points.Count];
            //for (int i = 0; i < intPoints.Length; i++)
            //{
            //    intPoints[i] = new Point((int)(points[i].X), (int)(points[i].Y));
            //}

            if(points.Count != 0) {
                Brush brush = new SolidBrush(fillColor);
                gfx.FillPolygon(brush, points.ToArray());
                //gfx.FillPolygon(Brushes.White, intPoints);
                gfx.DrawPolygon(Pens.Black, points.ToArray());
                brush.Dispose();
              //  gfx.DrawPolygon(Pens.Black, intPoints);
            }


        }
    }
}